package org.epanetgrid.otimizacao.searchspace;

import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.SimpleTimeZone;
import java.util.TimeZone;

import org.epanetgrid.otimizacao.log.AGLogger;
import org.epanetgrid.otimizacao.util.OtimizacaoUtil;
import org.gridfaith.GridFaithPool;
import org.gridfaith.GridServiceException;
import org.gridfaith.interfaces.GridRunnable;
import org.gridfaith.interfaces.GridService;
import org.jheuristics.Individual;
import org.jheuristics.util.BitString;


/**
 *
 * Implementa��o default de BulkIndividualEvaluator. Para todos os indiv�duos passados, o evaluator
 * retorna o objeto relacionado correspondente, retornando assim a lista com os indiv�duos "evoluidos".
 * Diferente do SimpleBulkIndividualEvaluator pois os indiv�duos s�o executados em uma grade computacional
 * @author Marcell Manfrin, marcell@ourgrid.org, Oct 4, 2005
 * @author vinicius
 */
public class SPGridBulkIndividualEvaluator extends SimpleBulkIndividualEvaluator implements BulkIndividualEvaluator {
	/**
	 * 
	 */
	private static final long serialVersionUID = 4271915319881669440L;
	
	private String requirements;
	private String basePath;
	private int indPorTask;
	
	private AGLogger logger = AGLogger.getInstance();
	
	/**
	 * 
	 * @param evaluator Evaluator que analizar� os indiv�duos passados
	 */
	public SPGridBulkIndividualEvaluator(IndividualEvaluator evaluator) {
		this(evaluator, "", "", 1);
	}
	
	/**
	 * 
	 * @param evaluator Evaluator que analizar� os indiv�duos passados
	 * @param requirements requirements do job
	 * @param basePath Path onde se encontram as libs necess�rias para rodar remotamente
	 */
	public SPGridBulkIndividualEvaluator(IndividualEvaluator evaluator, String requirements, String basePath, int indPorTask) {
		super(evaluator);
		this.requirements = requirements;
		this.basePath = basePath;
		this.indPorTask = indPorTask;
	}
	
	/*
	 * (non-Javadoc)
	 * @see org.smartpumping.otimizacao.searchspace.BulkIndividualEvaluator#evaluate(java.util.List)
	 */
	public List evaluate(List individuals) {
		for (Iterator it = individuals.iterator(); it.hasNext(); ) {
			if (!(it.next() instanceof Individual)) {
				throw new IllegalArgumentException();
			}
		}
		List individuosSimulados = new LinkedList();
//		Individual[] results = new Individual[individuals.size()];
		// realizando simula��o
//		for (Iterator it = individuals.iterator(); it.hasNext(); ) {
			// criando o servi�o
			GridService service = null;
			try {
				
				service = GridFaithPool.getInstance().getService(GridFaithPool.GRIDSERVICE_EXECUTOR);
				service.setRequirements(requirements);
				service.setApplicationJar(new File(basePath + File.separator + "gridfaith-1.3.jar"));
				service.addLibrary(new File(basePath + File.separator + "joda-time-1.2.1.jar"));
				service.addLibrary(new File(basePath + File.separator + "log4j-1.2.13.jar"));
				service.addLibrary(new File(basePath + File.separator + "xstream-1.1.2.jar"));
				service.addLibrary(new File(basePath + File.separator + "xpp3-1.1.3.4d_b4_min.jar"));
				service.addLibrary(new File(basePath + File.separator + "epanetgrid.jar"));
				
			} catch (Exception e) {
				System.err.println("Foi imposs�vel abrir um servi�o de execu��o em grade.");
				e.printStackTrace();
				System.exit(1);
			}
			// selecionando individuos n�o simulados
//			int count = 0;
//			int i = 0;
			for (Object ind:individuals) {
				Individual individual = (Individual) ind;
				if (null == individual.getDataApplication()) {
				} else {
					individuosSimulados.add(individual);
				}
			}
			boolean temIndSemSImular = false;
			
			List<List<Individual>> individualsToGrid = getIndividuosToGrid(individuals);
			if (!individualsToGrid.isEmpty()) {
				logger.debug("Individuos a serem enviados para o Grid:");
			}
			for (List<Individual> listIndividual : individualsToGrid) {
				logger.debug("Grupo:");
				for (Individual individual : listIndividual) {
					logger.debug(OtimizacaoUtil.gensToString((List<BitString>) individual.getGens()) + individual.getDataApplication());
				}
				service.addGridRunnable(new ExecutaIndividualEvaluatorNoGrid(getIndividualEvaluator(), listIndividual));
				temIndSemSImular = true;
			}
			
			// se tiver individuos n�o simulados simule na grade
			if (temIndSemSImular) {
				List gridResult = null;
				while (null == gridResult) {
					try {
						gridResult = service.executeAll();
					} catch (GridServiceException e) {
						e.printStackTrace();
						System.err.println("Ocorreu um erro na grade. Tentando novamente.");
					}
				}
				
				
				logger.debug("Individuos após serem enviados para o Grid:");
//				i = 0;
				for (Iterator it1 = gridResult.iterator(); it1.hasNext(); ) {
					List<Individual> individuos = (List<Individual>) it1.next();
					for (Individual individual : individuos) {
						if(individual != null) {
							logger.debug(OtimizacaoUtil.gensToString((List<BitString>) individual.getGens()) + " " + individual.getDataApplication());
							individuosSimulados.add(individual);
							//System.out.println("INDIviduo que nao convergiu");
						} else {
							logger.debug(individual + "  >> NÃO CONVERGIU!");
						}
					}
					
				}
			}
//		}
		
		return individuosSimulados;
	}
	
	/**
	 * Faz a parti��o dos indiv�duos que precisam ser analizados em rela��o ao atributo indPorTask
	 * @param individuals Conjunto de indiv�duos
	 * @return Lista de listas contendo os indiv�duos que ser�o rodados em cada task no grid
	 */
	private List<List<Individual>> getIndividuosToGrid(List individuals) {
		ArrayList<Individual> individuosSemDataAplication = new ArrayList<Individual>();
		
		for (Object object : individuals) {
			if(((Individual)object).getDataApplication() == null) {
				individuosSemDataAplication.add(((Individual)object));
			}
		}
		
		System.out.println("Divisao: ");
		
		List<List<Individual>> result = new LinkedList<List<Individual>>();
		int cont = 0;
		List<Individual> temp = new LinkedList<Individual>();
		for (Individual individuo : individuosSemDataAplication) {
			cont++;
			temp.add(individuo);
			if(cont == indPorTask) {
				cont = 0;
				result.add(temp);
				temp = new LinkedList<Individual>();
			}
			
			
		}
		
		if(cont != indPorTask && !temp.isEmpty()) {
			System.out.print(temp.size()+" ");
			result.add(temp);
		}
		System.out.println();
		return result;
	}
	
}

/**
 * Executa um IndividualEvaluator para um individual
 */
class ExecutaIndividualEvaluatorNoGrid implements GridRunnable {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = -2795411206958027129L;
	private IndividualEvaluator evaluator;
	private List<Individual> individuos;
	private TimeZone timeZone = SimpleTimeZone.getDefault();
	
	/**
	 *
	 * @param evaluator
	 * @param individual
	 */
	public ExecutaIndividualEvaluatorNoGrid(IndividualEvaluator evaluator, List<Individual> individuos) {
		this.evaluator = evaluator;
		this.individuos = individuos;
	}
	
	/**
	 * Metodo rodado remotamente
	 * @return Lista de indiv�duos devidamente analizados
	 * @see org.gridfaith.interfaces.GridRunnable#run()
	 */
	public Object run() {
		TimeZone.setDefault(timeZone);
		List<Individual> result = new LinkedList<Individual>();
		for (Individual individual : individuos) {
			result.add(evaluator.evaluate(individual));
		}
		return result;
	}
	
}
